using System;
using System.Collections.Generic;
using System.IO;

namespace Gloom
{
	public static class EnumSpecParser
	{
		static Dictionary<string, Dictionary<string, string>> lookup = new Dictionary<string, Dictionary<string, string>>();
		
		static List<KeyValuePair<string, List<string>>> enumAliases = new List<KeyValuePair<string, List<string>>>();
		
		public static List<string> SuccessEnums = new List<string>();
		
		private static void firstPassLoadLookupValues(List<List<string>> blocks)
		{
			List<string> alia = new List<string>();
			
			// First pass: Look up enum values. Ignore 'use' statements.
			foreach (List<string> block in blocks)
			{
				if (!block[0].Contains("enum:") || block[0] == "Boolean enum:") // i.e. 'not define:'
					continue;
				
				string[] nameLineBits = block[0].Split(' ');
				string name = nameLineBits[0];
				
				#region Cope with aliases
				if (nameLineBits.Length > 2)
				{
					name = "";
					for (int i = 0; i < nameLineBits.Length; i++)
					{
						if (nameLineBits[i].Contains("enum:") || nameLineBits[i].Contains("("))
						{
							name = nameLineBits[i - 1];
							break;
						}
						alia.Add(nameLineBits[i].Replace(",", ""));
					}
					if (name == "")
						name = nameLineBits[nameLineBits.Length - 2];
				}
				
				if (block.Count == 1)  // 'empty' block?
				{
					alia.Add(name);
					continue;
				}
				else if (alia.Count > 0)
				{
					enumAliases.Add(new KeyValuePair<string, List<string>>(name, new List<string>(alia)));
					alia.Clear();
				}
				#endregion
				
				if (!lookup.ContainsKey(name))
					lookup.Add(name, new Dictionary<string, string>());
				
				Dictionary<string, string> values = lookup[name];
				
				block.RemoveAt(0);
				
				foreach (string constant in block)
				{
					string[] bits = constant.Split(new char[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
					
					if (bits[0] == "use")
					{
						if (!values.ContainsKey(bits[2]))
							values.Add(bits[2], "use " + bits[1] + " " + bits[2]);
						continue;
					}
					
					if (bits[0].StartsWith("OES_")) // OpenGL ES! eww!!
						continue;
					if (bits[2].Length > 10)
						continue;  // probably that damn garbage TIMEOUT_IGNORED constant
					if (bits[1] != "=")
						throw new Exception("Parse FAIL: in EnumSpecParser.Parse(), bits[1] is not =!  (Raw line: `" + constant + "')");
					if (bits.Length != 3)
						throw new Exception("Parsing failure: in EnumSpecParser.Parse(), expected 3 string bits, but got a different string bits count!  (Raw line: `" + constant + "')");
					
					if (!values.ContainsKey(bits[0]))
						values.Add(bits[0], bits[2]);
				}
			}
		}
		
		private static void secondPassReplaceUseValues(out bool needRepeatPass)
		{
			needRepeatPass = false;
			
			// 
			// Second pass: Replace 'use OtherEnum CONST_NAME' with a real value.
			// 
			foreach (Dictionary<string, string> enumBlock in lookup.Values)
			{
				Dictionary<string, string> toReplace = new Dictionary<string, string>();
				
				foreach (string key in enumBlock.Keys)
				{
					string val = enumBlock[key];
					
					if (val.StartsWith("use "))
					{
						string[] bits = val.Split(' ');
						
						//enumBlock[key] = lookup[bits[1]][bits[2]];
						Console.WriteLine("Looking up: `" + bits[1] + "'::`" + bits[2] + "'");
						
						if (!lookup.ContainsKey(bits[1]) || !(lookup[bits[1]]).ContainsKey(bits[2])) // there seems to be missing consts in enum.spec :/
						{
							if (!toReplace.ContainsKey("§±" + key))
								toReplace.Add("§±" + key, "FAIL");
						}
						else
						{
							if (!toReplace.ContainsKey(key))
							{
								if (lookup[bits[1]][bits[2]].StartsWith("use "))
									needRepeatPass = true;
								toReplace.Add(key, lookup[bits[1]][bits[2]]);
							}
						}
						
						//if (enumBlock[key].StartsWith("use "))
						//	throw new Exception("'use' enum redirection > 1 level deep!  Can't handle this.");
					}
				}
				
				foreach (string k in toReplace.Keys)
				{
					if (k.StartsWith("§±"))
					{
						enumBlock.Remove(k.Substring(2));
						if (enumBlock.ContainsKey("GLOOM_TRANSLATION_ERROR_" + k.Substring(2)))
							continue;
						enumBlock.Add("GLOOM_TRANSLATION_ERROR_" + k.Substring(2), "0");
					}
					else
						enumBlock[k] = toReplace[k];
				}
			}
		}
		
		public static void ParseAndWrite(List<List<string>> blocks)
		{
			firstPassLoadLookupValues(blocks);
			
			bool bRepeat = true;
			int repeated = 0;
			while (bRepeat)
			{
				if (repeated > 10)
					throw new Exception("Recursive substitute failure.");
				
				secondPassReplaceUseValues(out bRepeat);
				repeated++;
				Console.WriteLine("ITERATION!!!!!!!!");
			}
			Console.WriteLine("Took " + repeated.ToString() + " iterations.");
			
			// 
			// Third pass: Sort out aliases.
			//
			foreach (KeyValuePair<string, List<string>> kvpa in enumAliases)
			{
				string copyFrom = kvpa.Key; // enum to copy constants from
				List<string> copyTo = kvpa.Value; // enum(s) to copy constants to
				
				Dictionary<string, string> srcEnum = lookup[copyFrom];
				
				foreach (string cte in copyTo)
				{
					if (cte.Trim().Length < 1)
						continue;
					
					// Find/create enum!
					Dictionary<string, string> dstEnum;
					if (!lookup.ContainsKey(cte))
						lookup.Add(cte, new Dictionary<string, string>());
					dstEnum = lookup[cte];
					
					foreach (KeyValuePair<string, string> atom in srcEnum)
						if (!dstEnum.ContainsKey(atom.Key))
							dstEnum.Add(atom.Key, atom.Value);
				}
			}
			
			// 
			// Fourth pass: Write enums!
			// 
			
			StreamWriter sw = new StreamWriter(File.Open(MainClass.LimeSourcePath + "GL.Enums.cs", FileMode.Create));
			sw.WriteLine("namespace Lime\n{\n"); // \tpublic static partial class GL\n\t{");
			
			foreach (KeyValuePair<string, Dictionary<string, string>> kvp in lookup)
			{
				if (NeatenEnumName(kvp.Key).EndsWith(",")) // hack: we don't understand some of the weirder declarations
					continue;
				
				//enumOut.Add("\t\tpublic enum " + kvp.Key);
				//enumOut.Add("\t\t{");
				sw.WriteLine("\t\tpublic enum " + NeatenEnumName(kvp.Key) + " : uint");
				sw.WriteLine("\t\t{");
				
				int c = kvp.Value.Count;
				foreach (KeyValuePair<string, string> constant in kvp.Value)
				{
					//enumOut.Add("\t\t\t" + NeatenConstantName(constant.Key) + " = " + constant.Value);
					c--;
					string line = "\t\t\t" + NeatenConstantName(constant.Key) + " = " + constant.Value + (c != 0 ? "," : "");
					sw.WriteLine(line);
				}
				
				//enumOut.Add("}\t\t");
				sw.WriteLine("\t\t}\n");
				
				SuccessEnums.Add(NeatenEnumName(kvp.Key));
				
				//enumOut.Clear();
			}
			
			//
			// Fifth pass: Write a massive super emergency-enum, because sometimes, they just ain't there in the specs.
			//
			List<string> alreadyDone = new List<string>();
			
			sw.WriteLine("\t\tpublic enum GLEnum : uint\t\t{\n");
			bool first = true;
			
			foreach (KeyValuePair<string, Dictionary<string, string>> kv in lookup)
			{
				foreach (KeyValuePair<string, string> dictp in kv.Value)
				{
					string n = NeatenConstantName(dictp.Key);
					
					if (!alreadyDone.Contains(n))
					{
						sw.Write((first ? "" : ",\n") + "\t\t\t" + n + " = " + dictp.Value);
						first = false;
						alreadyDone.Add(n);
					}
				}
			}
			
			sw.Write("\n\t\t}\n");
			
			SuccessEnums.Add("GLEnum");
			
			sw.WriteLine("\n}\n");
			sw.Flush();
			sw.Close();
			
			Console.WriteLine("Wrote GL.Enums.cs.");
		}
				
		public static string NeatenConstantName(string name)
		{
			// Input: SOME_UGLY_MONSTROSITY
			// Output: SomeUglyMonstrosity
			
			char[] outc = new char[name.Length];
			int l = 0;
			
			bool high = true; // b/c first letter needs to be caps even though there is no preceding underscore
			
			foreach (char c in name)
			{
				if (high)
				{
					outc[l] = char.ToUpper(c);
					high = false;
					l++;
					
					if (char.ToLower(c) == char.ToUpper(c))
						high = true; // incredibly obscure change, to make Texture2d into Texture2D
					
					continue;
				}
				
				// If it's an underscore, drop it, and make the next letter a capital letter.
				if (c == '_')
					high = true;
				else
				{
					outc[l] = char.ToLower(c);
					l++;
				}
			}
			
			return ensureAlphabeticStart(new string(outc, 0, l));
		}
		
		private static string ensureAlphabeticStart(string name)
		{
			if ( (!(name[0] >= 'A' && name[0] <= 'Z')) && (!(name[0] >= 'a' && name[0] <= 'z')) )
				return "_" + name;
			return name;
		}
		
		public static string NeatenEnumName(string name)
		{
			return ensureAlphabeticStart(name); // + "Enum";
		}
	}
}
